﻿using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using rpc_service.cache;

namespace rpc_service.core.support.assembly.il.trycatch;

public class ILTryCatch
{
    private Label tryBlock;
    private LocalBuilder exceptionLocal;
    
    public void StartTryBlock(ILGenerator il)
    {
        exceptionLocal = il.DeclareLocal("Exception".GetTypeByStr());
        tryBlock = il.BeginExceptionBlock(); 
    }

    public void CatchBlock(ILGenerator il)
    {
        il.BeginCatchBlock("WebException".GetTypeByStr()); // Catch WebException specifically
        il.Emit(OpCodes.Stloc, exceptionLocal);
        // Create and throw RpcNetworkException
        il.Emit(OpCodes.Ldstr, "A network error occurred during RPC call.");
        il.Emit(OpCodes.Ldloc, exceptionLocal); // Inner exception
        ConstructorInfo? rpcNetworkExCtor = "Exception".GetTypeByStr().GetConstructor(new Type[] { "string".GetTypeByStr(), "Exception".GetTypeByStr() });
        if (rpcNetworkExCtor != null)
        {
            il.Emit(OpCodes.Newobj, rpcNetworkExCtor);
            il.Emit(OpCodes.Throw);
        }
        else
        { // Fallback if constructor not found (should not happen if defined correctly)
            il.Emit(OpCodes.Ldloc, exceptionLocal);
            il.Emit(OpCodes.Throw);
        }

        il.BeginCatchBlock("Exception".GetTypeByStr()); // Catch any other exception
        il.Emit(OpCodes.Stloc, exceptionLocal);
        // Create and throw RpcBaseException (or a more generic RpcOperationException)
        il.Emit(OpCodes.Ldstr, "An unexpected error occurred during HTTP request processing.");
        il.Emit(OpCodes.Ldloc, exceptionLocal); // Inner exception
        ConstructorInfo? rpcBaseExCtor = "Exception".GetTypeByStr().GetConstructor(new Type[] { "string".GetTypeByStr(), "Exception".GetTypeByStr() });
        if (rpcBaseExCtor != null)
        {
            il.Emit(OpCodes.Newobj, rpcBaseExCtor);
            il.Emit(OpCodes.Throw);
        }
        else
        {
            il.Emit(OpCodes.Ldloc, exceptionLocal);
            il.Emit(OpCodes.Throw);
        }
    }

    public void FinallyBlock(ILGenerator il,
        LocalBuilder requestLocal,
        LocalBuilder responseLocal,
        LocalBuilder receiveStreamLocal,
        LocalBuilder readStreamLocal)
    {
        il.BeginFinallyBlock();

        if (receiveStreamLocal != null)
        {
            // Close StreamReader if not null
            Label skipReadStreamClose = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, readStreamLocal);
            il.Emit(OpCodes.Brfalse_S, skipReadStreamClose);
            il.Emit(OpCodes.Ldloc, readStreamLocal);
            il.Emit(OpCodes.Callvirt, typeof(StreamReader).GetMethod("Close")!);
            il.MarkLabel(skipReadStreamClose);
        }

        if (receiveStreamLocal != null)
        {
            // Close StreamReader if not null
            Label skipReadStreamClose = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, receiveStreamLocal);
            il.Emit(OpCodes.Brfalse_S, skipReadStreamClose);
            il.Emit(OpCodes.Ldloc, receiveStreamLocal);
            il.Emit(OpCodes.Callvirt, typeof(StreamReader).GetMethod("Close")!);
            il.MarkLabel(skipReadStreamClose);
        }
        
        if (responseLocal != null)
        {
            // Close HttpWebResponse if not null
            Label skipResponseClose = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, responseLocal);
            il.Emit(OpCodes.Brfalse_S, skipResponseClose);
            il.Emit(OpCodes.Ldloc, responseLocal);
            il.Emit(OpCodes.Callvirt, typeof(HttpWebResponse).GetMethod("Close")!);
            il.MarkLabel(skipResponseClose);
        }
    }

    public void EndTryBlock(ILGenerator il)
    {
        il.EndExceptionBlock();
    }
}